package com.google.code.jmarias.server.tcp;

import com.google.code.jmarias.common.NewTable;
import com.google.code.jmarias.common.AbstractUser;
import com.google.code.jmarias.common.action.SignedAction;
import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * Musi byt thread-safe, protoze soucasne k nemu muze pristupovat i timer.
 *
 * @author Ondrej Michal
 */
public class Room {

    private static Log log = LogFactory.getLog(Room.class);

    private Long id;

    /** Tovarna her, ktere se v teto mistnosti hraji. */
    private AbstractGameFactory factory;

    private Date creationTime = new Date();

    private AbstractGame currentGame = null;

    // @TODO je 10 akorat?
    // @TODO timer se vytvari pro kazdy stul, asi by bylo lepsi mit jeden a predavat ho sem
    private ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(10);

    /** zidle u stolu, ke kterym si mohou hraci sedat */
    private ConcurrentMap<Integer, AbstractContactableUser> chairs = new ConcurrentHashMap<Integer, AbstractContactableUser>();

    public Room(BlockingQueue<SignedAction> clientToServerActions, Long id, AbstractGameFactory factory, AbstractContactableUser client) {
        this.id = id;
        this.factory = factory;
        sitDown(client);

        BotContactableUser bot1 = new BotContactableUser(this);
        bot1.setUsername("bot1");
        bot1.setServer(new LocalServer(clientToServerActions, bot1));
        chairs.put(1, bot1);
        BotContactableUser bot2 = new BotContactableUser(this);
        bot2.setUsername("bot2");
        bot2.setServer(new LocalServer(clientToServerActions, bot2));
        chairs.put(2, bot2);

        // @TODO jen pro testovani
        timer.schedule(new Runnable() {

            public void run() {
                // bez try catch to ignoruje vsechny vyjimky, ktere vzniknou
                try {
                    timeoutElapsed();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }, 1, TimeUnit.SECONDS);
    }

    // vola se z jineho vlakna (timeru), proto asi cela trida musi byt thread safe
    public synchronized void timeoutElapsed() {
        log.debug("V mistnosti " + id + " uplynul minimalni casovy limit pro prihlaseni novych hracu. Pokud je dost hracu, hra muze zacit.");
        if (chairs.size() >= factory.getMinPlayers()) {
            startGame();
        }
        // @TODO else nastavit priznak, ze 10 sekund ubehlo a jakmile se pripoji dalsi hrac,
        // kontrolovat, jestli uz neni splnena podminka minimalniho poctu hracu
    }

    private void startGame() {
        log.debug("V mistnosti " + id + " spoustim novou hru.");
        NewTable table = new NewTable();
        for (Integer key : chairs.keySet()) {
            if (chairs.get(key) != null) {
                table.addPlayer(new ContactablePlayer(chairs.get(key)));
            }
        }
        currentGame = factory.createGame(table);
    }

    /**
     * Pokusi klienta posadit k tomuto stolu na pozici position. Vraci true,
     * pokud vse probehlo v poradku, false, pokud ho nekdo predbehl.
     * @TODO navratova hodnota by asi mela byt nahrazena vyjimkou
     */
    public boolean sitDown(int position, AbstractContactableUser client) {
        AbstractUser previous = chairs.putIfAbsent(position, client);
        return previous == null;
    }

    /** Pripoji klienta na prvni volne misto u stolu, vrati pozici nebo -1 pokud je plno. */
    public int sitDown(AbstractContactableUser client) {
        for (int i = 0; i < factory.getMaxPlayers(); i++) {
            if (chairs.get(i) == null) {
                AbstractUser previous = chairs.putIfAbsent(i, client);
                if (previous == null) {
                    return i;
                }
            }
        }
        return -1;
    }

    public ConcurrentMap<Integer, AbstractContactableUser> getChairs() {
        return chairs;
    }

    public AbstractGame getCurrentGame() {
        return currentGame;
    }

    public Long getId() {
        return id;
    }

}
